這幾天太忙碌了,試著撰寫路由的地方有點卡進度。
今天先來往下研究資料庫相關的內容。
要建立資料庫,在 Laravel 裡面首先要先建立 migration。
通常來說會使用指令做這件事情:
php artisan make:migration create_flights_table
這段指令是怎麼做的呢?我們來搜尋看看
首先我們找到 make:migration
實際呼叫的程式,這個在 MigrateMakeCommand
裡面
namespace Illuminate\Database\Console\Migrations
#[AsCommand(name: 'make:migration')]
class MigrateMakeCommand extends BaseCommand
我們追著繼承鏈一路往上追
use Illuminate\Console\Command;
class BaseCommand extends Command
class Command extends SymfonyCommand
該說不令人意外嗎?這個部分也是繼承了 Symfony 的套件,這邊使用的是 symfony/console
這個套件的功能,協助我們撰寫 CLI 工具。
我們繼續看看
/**
* The console command signature.
*
* @var string
*/
protected $signature = 'make:migration {name : The name of the migration}
{--create= : The table to be created}
{--table= : The table to migrate}
{--path= : The location where the migration file should be created}
{--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}
{--fullpath : Output the full path of the migration (Deprecated)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new migration file';
這邊定義了指令的使用方式。至於實際的運作邏輯,定義在 handle()
裡面
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
// It's possible for the developer to specify the tables to modify in this
// schema operation. The developer may also specify if this table needs
// to be freshly created so we can create the appropriate migrations.
$name = Str::snake(trim($this->input->getArgument('name')));
$table = $this->input->getOption('table');
$create = $this->input->getOption('create') ?: false;
// If no table was given as an option but a create option is given then we
// will use the "create" option as the table name. This allows the devs
// to pass a table name into this option as a short-cut for creating.
if (! $table && is_string($create)) {
$table = $create;
$create = true;
}
// Next, we will attempt to guess the table name if this the migration has
// "create" in the name. This will allow us to provide a convenient way
// of creating migrations that create new tables for the application.
if (! $table) {
[$table, $create] = TableGuesser::guess($name);
}
// Now we are ready to write the migration out to disk. Once we've written
// the migration out, we will dump-autoload for the entire framework to
// make sure that the migrations are registered by the class loaders.
$this->writeMigration($name, $table, $create);
}
前面的部分是用來判斷使用者輸入,以及試著從使用者輸入的 migration 名稱判斷他想建立的表名,這邊我們先不看這段,關注於 writeMigration()
的邏輯
protected function writeMigration($name, $table, $create)
{
$file = $this->creator->create(
$name, $this->getMigrationPath(), $table, $create
);
$this->components->info(sprintf('Migration [%s] created successfully.', $file));
}
create()
的實作邏輯則是
/**
* Create a new migration at the given path.
*
* @param string $name
* @param string $path
* @param string|null $table
* @param bool $create
* @return string
*
* @throws \Exception
*/
public function create($name, $path, $table = null, $create = false)
{
$this->ensureMigrationDoesntAlreadyExist($name, $path);
// First we will get the stub file for the migration, which serves as a type
// of template for the migration. Once we have those we will populate the
// various place-holders, save the file, and run the post create event.
$stub = $this->getStub($table, $create);
$path = $this->getPath($name, $path);
$this->files->ensureDirectoryExists(dirname($path));
$this->files->put(
$path, $this->populateStub($stub, $table)
);
// Next, we will fire any hooks that are supposed to fire after a migration is
// created. Once that is done we'll be ready to return the full path to the
// migration file so it can be used however it's needed by the developer.
$this->firePostCreateHooks($table, $path);
return $path;
}
這邊開始遇到一個很有意思的邏輯。
我們都知道在 Laravel 的 make
指令會協助我們建立檔案,但是很少會需要自己撰寫這樣的功能,或者去研究 Laravel 怎麼做到這件事情。
在這邊我們開始可以看到,這整段的邏輯大概是取得樣板的 getStub()
、取得路徑的 getPath()
、確認路徑上資料夾存在的 ensureDirectoryExists()
最後將檔案透過 populateStub()
寫入。
這邊的 getStub()
實作為
/**
* Get the migration stub file.
*
* @param string|null $table
* @param bool $create
* @return string
*/
protected function getStub($table, $create)
{
if (is_null($table)) {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.stub')
? $customPath
: $this->stubPath().'/migration.stub';
} elseif ($create) {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.create.stub')
? $customPath
: $this->stubPath().'/migration.create.stub';
} else {
$stub = $this->files->exists($customPath = $this->customStubPath.'/migration.update.stub')
? $customPath
: $this->stubPath().'/migration.update.stub';
}
return $this->files->get($stub);
}
這邊我們可以看到,根據我們的操作是一般的 migration、建立資料表或者更改資料表,會有不同的樣板可以使用。
我們先看看一般的 migration.stub
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
//
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};
到這邊我們就可以概略地知道,當我們下一個 make:migration
指令時,Laravel 會利用 SymfonyCommand
,加上自己之前建立好的樣板,來協助寫入檔案到專案內。